#ifndef _PY_BOOST_FUNCTION_HPP_
#define _PY_BOOST_FUNCTION_HPP_ 

#include <boost/python.hpp>

#include <boost/function.hpp>
#include <boost/function_types/function_type.hpp>
#include <boost/function_types/result_type.hpp>
#include <boost/function_types/is_function.hpp>

#include <boost/type_traits/function_traits.hpp>

#include <boost/preprocessor/repetition/enum_binary_params.hpp>
#include <boost/preprocessor/repetition/enum_params.hpp>
#include <boost/preprocessor/repetition/repeat.hpp>
#include <boost/preprocessor/repetition/repeat_from_to.hpp>

#include <boost/python/ptr.hpp>
#include <boost/python/call_method.hpp>
#include <boost/system/error_code.hpp>

#include <common/logger.hpp>
#include <lib/scan_struct.hpp>

namespace detail{

	template <typename RT, int arity>
	struct pyobject_invoker;

	template <typename RT> 
	struct pyobject_invoker<RT, 0 > 
	{ 
		boost::python::object callable; 

		RT operator( )( )
		{
			return boost::python::extract<RT>( callable( ) );
		}
	};

	template <>
	struct pyobject_invoker<void, 0>
	{
		boost::python::object callable;

		void operator()()
		{
			LOG(DEBUG, "before call callable, callable="<<(void*)callable.ptr());
			callable();
			LOG(DEBUG, "after call callable");
		}
	};

#define MAKE_PYOBJECT_INVOKER(z, n, data) \
	template <typename RT> \
	struct pyobject_invoker<RT, n > \
	{ \
		boost::python::object callable; \
		\
		template < BOOST_PP_ENUM_PARAMS(n, typename Arg) > \
		RT operator( )(BOOST_PP_ENUM_BINARY_PARAMS(n, Arg, arg ) )\
		{\
			boost::python::object o = callable( BOOST_PP_ENUM_PARAMS(n, arg) );\
			return boost::python::extract<RT>( o );\
		}\
	};

	BOOST_PP_REPEAT_FROM_TO( 1, 6, MAKE_PYOBJECT_INVOKER, nothing );

	#undef MAKE_PYOBJECT_INVOKER

//#define MAKE_PYOBJECT_INVOKER(z, n, data) \
	template <> \
	struct pyobject_invoker<void, n > \
	{ \
		boost::python::object callable; \
		\
		template < BOOST_PP_ENUM_PARAMS(n, typename Arg) > \
		void operator( )(BOOST_PP_ENUM_BINARY_PARAMS(n, Arg, arg ) )\
		{\
			LOG(DEBUG, "before call callable, argnument count="<<n);\
			callable( BOOST_PP_ENUM_PARAMS(n, arg) );\
			LOG(DEBUG, "after call callable");\
		}\
	};

//	BOOST_PP_REPEAT_FROM_TO( 1, 6, MAKE_PYOBJECT_INVOKER, nothing );

	#undef MAKE_PYOBJECT_INVOKER

	template <>
	struct pyobject_invoker<void, 2>
	{
		boost::python::object callable;

		void operator( )(xbase::row_result::ptr_type row_ptr, boost::system::error_code ec)
		{
			LOG(DEBUG, "before call callable, row_ptr="<<row_ptr.get()<<" ec="<<ec
				<< " callable=" << callable.ptr());
			callable(row_ptr, ec);
			//boost::python::call_method<void>(callable.ptr(), "__call__", row_ptr, ec);
			//PyEval_CallFunction(callable.ptr(), "i", 1);
			LOG(DEBUG, "after call callable");
		}
	};

	template <typename FT>
		boost::function< FT > function_frompyobj( boost::python::object o )
	{
		const int arity = boost::function_traits< FT >::arity;
		typedef 
			typename  boost::function_types::result_type< FT >::type
			result_type;
		pyobject_invoker<result_type, arity > inv;
		inv.callable = o;

		LOG(DEBUG, "callable=" << o.ptr());
		return inv;
	}

} // namespace detail

template <typename FT>
void def_function(const char* func_name, const char* func_doc = 0)
{
	BOOST_STATIC_ASSERT( boost::function_types::is_function< FT >::value ) ;
	namespace bp = boost::python;
	typedef boost::function<FT> function_t;
	bp::class_< function_t >
		(func_name, func_doc, bp::no_init)
		.def("__call__", &function_t::operator() )
		.def("from_callable", &detail::function_frompyobj<FT> )
		.staticmethod("from_callable")
		;
} // def_function0




#endif // _PY_BOOST_FUNCTION_HPP_
